﻿using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LiveChartsCore.SkiaSharpView.Drawing.Geometries;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore;
using Parquet;
using Parquet.Data;
using Parquet.Schema;
using Parquet.Serialization;
using Parquet.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using LiveChartsCore.Defaults;
using SkiaSharp;
using Microsoft.Extensions.DependencyInjection;
using LiveChartsCore.SkiaSharpView.Painting;
using CommunityToolkit.Mvvm.Messaging;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using System.Xml.Linq;
using static System.Runtime.InteropServices.JavaScript.JSType;
using static SkiaSharp.HarfBuzz.SKShaper;
using Ursa.Controls;


namespace DataAnalysis.ViewModels
{
    public class SelectedDataRange
    {
        public long Start { get; set; }
        public long End { get; set; }
    }
    public partial class MainViewModel : ObservableRecipient
    {
        protected override void OnActivated()
        {
            WeakReferenceMessenger.Default.Register<SelectedDataRange, string>(this, "updateSelectedDataRange", (obj, range) => { SelectedDataRangeStart = range.Start; SelectedDataRangeEnd = range.End; });
        }
 
        protected override void OnDeactivated()
        {
            base.OnDeactivated();
            WeakReferenceMessenger.Default.Unregister<SelectedDataRange>(this);
        }
        public MainViewModel()
        {
            IsActive = true;
            dataManager = new(DataRootDirectory);
        }
        [ObservableProperty]
        //private string dataRootDirectory = "./data/";
        private string dataRootDirectory = @"F:\Test\data";
        [ObservableProperty]
        private List<string>? dataFiles;
        [ObservableProperty]
        private string? selectedDataFileName;
        [ObservableProperty]
        private DataManager dataManager;
        [ObservableProperty]
        private MetaData? currentMetaData;
        private const int dataCounts =  10 * 3600 * 24;
        private const string dataFilePath = "./data.dat";
        [ObservableProperty]
        private long dataCount;
        [ObservableProperty]
        private string? prefix;
        [ObservableProperty]
        private List<string>? channelNames;
        [ObservableProperty]
        private string? selectedChannelName;
        [ObservableProperty]
        private ISeries[] trendSeries = [new LineSeries<double>() {
            Fill = null,
            GeometryFill = null,
            GeometryStroke = null,
            Stroke = new SolidColorPaint(SKColors.Blue) { StrokeThickness = 1 },
                            LineSmoothness = 0,
        }];
        //[ObservableProperty]
        //private ISeries[] series = [
        //    new HeatSeries<WeightedPoint>
        //    {
        //        HeatMap = [
        //            //new SKColor(255, 241, 118).AsLvcColor(), // the first element is the "coldest"
        //            SKColors.Green.AsLvcColor(), // the first element is the "coldest"
        //            SKColors.DarkSlateGray.AsLvcColor(),
        //            SKColors.Red.AsLvcColor() // the last element is the "hottest"
        //        ],
        //        MiniatureShapeSize = 44,
        //        Values = [
        //            // Charles
        //            new(0, 0, 150), // Jan
        //            new(0, 1, 123), // Feb
        //            new(0, 2, 310), // Mar
        //            new(0, 3, 225), // Apr
        //            new(0, 4, 473), // May
        //            new(0, 5, 373), // Jun

        //            // Richard
        //            new(1, 0, 432), // Jan
        //            new(1, 1, 312), // Feb
        //            new(1, 2, 135), // Mar
        //            new(1, 3, 78), // Apr
        //            new(1, 4, 124), // May
        //            new(1, 5, 423), // Jun

        //            // Ana
        //            new(2, 0, 543), // Jan
        //            new(2, 1, 134), // Feb
        //            new(2, 2, 524), // Mar
        //            new(2, 3, 315), // Apr
        //            new(2, 4, 145), // May
        //            new(2, 5, 80), // Jun

        //            // Mari
        //            new(3, 0, 90), // Jan
        //            new(3, 1, 123), // Feb
        //            new(3, 2, 70), // Mar
        //            new(3, 3, 123), // Apr
        //            new(3, 4, 432), // May
        //            new(3, 5, 142), // Jun
        //        ]
        //    }
        //];
        [RelayCommand]
        private async Task Test()
        {
            Debug.WriteLine("Test");
            Random random = new();
            List<ChannelInfo> channels = [];

            const int low = 15000;
            const int high = 300_000_000;
            int[] rise = [30_000_000, 70_000_000, 150_000_000, 225_000_000, 260_000_000];
            int[] fall = [260_000_000, 225_000_000, 150_000_000, 70_000_000, 30_000_000];
            for (int i = 1; i <= 64; i++)
            {
                channels.Add(new ChannelInfo
                {
                    Digits = 8,
                    Name = $"通道{i}",
                    Unit = "A",
                    Trigger = new TriggerInfo
                    {
                        Type1 = TriggerType.GreaterThan,
                        Value1 = low,
                        Logic = Logic.And,
                        Type2 = TriggerType.LessThan,
                        Value2 = high
                    }
                });
            }

            DataManager.Start(1000, channels, Prefix);

            ulong count = 0;

            (int hour, int minute)[] points1 =
            {
                (0,45),
                (0,33),
                //(102,45),
                //(221,33),
                (252,48),
                (550,2),
                (601,15),
                (704,22),
                (777,47),
                (777,29),
                (980,27),

                (102,55),
                (291,51),
                (333,50),
                (451,59),
                (558,54),
                (911,54),
                (911,56),
            };

            (int hour, int minute)[] points2 =
            {
                (50,35),
                (220,13),

                (255,58),
                (520,52),
                (520,55),
                (610,52),
                (777,55),
                (888,59),
                (941,57),
                (102,55),
                (291,51),
                (333,50),
                (451,59),
                (558,54),
                (911,54),
                (911,56),
                (911,56),
            };

            int[] values = new int[64];
            //await Task.Run(() =>
            {
                DateTime startTime = DateTime.Now;
                for (int hour = 0; hour < 1000; hour++)
                {
                    if (DataManager.BufferCount >= 120_000 && (DateTime.Now - startTime).TotalSeconds < 5)
                    {
                        await Task.Delay(100);
                        continue;
                    }
                    startTime = DateTime.Now;

                    int milliSecond = 0;
                    int minute;
                    int index;
                    for (; milliSecond < 50 * 60 * 1000 - 5; milliSecond++)
                    {
                        minute = milliSecond / 1000 / 60;
                        foreach(var point in points1)
                        {
                            if(point.hour == hour && point.minute == minute && milliSecond % 60000 == 0)
                            {
                                values[0] = random.Next(low + 10, high);
                                break;
                            }
                            values[0] = random.Next(0, low);
                        }
                        foreach(var point in points2)
                        {
                            if(point.hour == hour && point.minute == minute && milliSecond % 60000 == 0)
                            {
                                values[1] = random.Next(low + 10, high);
                                break;
                            }
                            values[1] = random.Next(0, low);
                        }
                        for (int i = 2; i < 64; i++)
                        {
                            values[i] = random.Next(0, low);
                        }
                        DataManager.AppendData(0, values);
                        count++;
                        while(DataManager.BufferCount >= 1000_000)
                        {
                            await Task.Delay(100);
                        }
                    }
                    //上升沿
                    index = 0;
                    for (; milliSecond < 50 * 60 * 1000; milliSecond++)
                    {
                        for (int i = 0; i < 64; i++)
                        {
                            values[i] = rise[index];
                        }
                        index++;
                        DataManager.AppendData(0, values);
                        count++;
                        while(DataManager.BufferCount >= 1000_000)
                        {
                            await Task.Delay(100);
                        }
                    }
                    for (; milliSecond < 3_600_000 - 5; milliSecond++)
                    {
                        minute = milliSecond / 1000 / 60;
                        foreach (var point in points1)
                        {
                            if (point.hour == hour && point.minute == minute && milliSecond % 60000 == 0)
                            {
                                values[0] = random.Next(low + 10, 200_000_000);
                                break;
                            }
                            values[0] = random.Next(high - 1_000_000, high + 1_000_000);
                        }
                        foreach (var point in points2)
                        {
                            if (point.hour == hour && point.minute == minute && milliSecond % 60000 == 0)
                            {
                                values[1] = random.Next(low + 10, 200_000_000);
                                break;
                            }
                            values[1] = random.Next(high - 1_000_000, high + 1_000_000);
                        }
                        for (int i = 2; i < 64; i++)
                        {
                            values[i] = random.Next(high - 1_000_000, high + 1_000_000);
                        }
                        DataManager.AppendData(0, values);
                        count++;
                        while (DataManager.BufferCount >= 1000_000)
                        {
                            await Task.Delay(100);
                        }
                    }
                    //下降沿
                    index = 0;
                    for (; milliSecond < 3_600_000; milliSecond++)
                    {
                        for (int i = 0; i < 64; i++)
                        {
                            values[i] = fall[index];
                        }
                        index++;
                        DataManager.AppendData(0, values);
                        count++;
                        while (DataManager.BufferCount >= 1000_000)
                        {
                            await Task.Delay(100);
                        }
                    }
                    DataCount = (int)count;
                    Debug.WriteLine("写入");
                    //for (int j = 0; j < 1000_000; j++)
                    //{
                    //    DataManager.AppendData(count * 1000 * 1,
                    //    [
                    //        //(int)(Math.Sin((double)count / 1000.0) * 1000),
                    //        j,
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //        random.Next(0,10000),
                    //    ]);
                    //    count++;
                    //}
                }
                DataManager.Stop();
            }
            //);

        }

        [RelayCommand]
        public async Task ListAllDataFiles()
        {
            if(DataManager == null)
            {
                DataFiles = null;
                return;
            }
            DataFiles = await DataManager.ListAllDataFiles();
        }

        [RelayCommand]
        private async Task GenerateData()
        {
            Debug.WriteLine("生成数据");
            DataCount = 0;


            //// create file schema
            //var schema = new ParquetSchema(
            //    new DataField<int>("time"),
            //    new DataField<int>("value"));

            ////create data columns with schema metadata and the data you need
            //var idColumn = new DataColumn(
            //   schema.DataFields[0],
            //   new int[] { 1, 2 });

            //var cityColumn = new DataColumn(
            //   schema.DataFields[1],
            //   new int[] { 3, 4 });

            var random = new Random();

            List<int[]> list = new List<int[]>();
            for (int i = 0; i < 10; i++)
            {
                list.Add(Enumerable.Range(0, dataCounts).Select(i => random.Next(0, 1000)).ToArray());
            }


            await AppendData(Enumerable.Range(0, dataCounts).Select(i => (long)i).ToArray(), 
                Enumerable.Range(0, 10).Select(i => $"ch{i+1}").ToList(), list
                );
            //await ParquetSerializer.SerializeAsync(data, "./data.dat");
        }

        [RelayCommand]
        private void Stop()
        {
            DataManager?.Stop();
        }

        
        private async Task AppendData(long[] times, List<string> names, List<int[]> channels)
        {
            if(times == null || names == null || channels == null)
            {
                return;
            }
            if (times.Length == 0)
            {
                return;
            }
            if (channels.Count == 0)
            {
                return;
            }

            
            List<DataField> fields = new List<DataField>();
            fields.Add(new DataField<long>("time"));
            foreach (var name in names)
            {
                fields.Add(new DataField<int>(name));
            }

            var schema = new ParquetSchema(fields);

            List<DataColumn> columns = [];
            for (int i = 0; i < fields.Count; i++)
            {
                if(i == 0)
                {
                    columns.Add(new DataColumn(schema.DataFields[0], times));
                }
                else
                {
                    columns.Add(new DataColumn(schema.DataFields[i], channels[i - 1]));
                }
            }
            Stopwatch sw = Stopwatch.StartNew();
            using (Stream fileStream = System.IO.File.OpenWrite(dataFilePath))
            {
                using (ParquetWriter parquetWriter = await ParquetWriter.CreateAsync(schema, fileStream))
                {
                    //parquetWriter.CompressionMethod = CompressionMethod.Gzip;
                    //parquetWriter.CompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
                    // create a new row group in the file
                    using (ParquetRowGroupWriter groupWriter = parquetWriter.CreateRowGroup())
                    {
                        foreach (var column in columns)
                        {
                            await groupWriter.WriteColumnAsync(column);
                        }
                    }
                }
            }
            using(Stream fs = File.OpenWrite(dataFilePath))
            {
                fs.Position = 0;
                using (ParquetWriter parquetWriter = await ParquetWriter.CreateAsync(schema, fs, append: true))
                {
                    //parquetWriter.CompressionMethod = CompressionMethod.Gzip;
                    //parquetWriter.CompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
                    // create a new row group in the file
                    using (ParquetRowGroupWriter groupWriter = parquetWriter.CreateRowGroup())
                    {
                        foreach (var column in columns)
                        {
                            await groupWriter.WriteColumnAsync(column);
                        }
                    }
                }
            }
                
            sw.Stop();
            Debug.WriteLine($"耗时{sw.ElapsedMilliseconds}ms");
        }

        [ObservableProperty]
        private long selectedDataRangeStart;
        [ObservableProperty]
        private long selectedDataRangeEnd;
        [ObservableProperty]
        private TrendData trendData = new();
        [ObservableProperty]
        private TrendDataPlotSettings trendDataPlotSettings = new();
        [ObservableProperty]
        private string? currentFilePath;
        [RelayCommand]
        private async Task ReadData()
        {
            
            var mainWindow = App.Current.Services.GetService<MainWindow>();
            var topLevel = TopLevel.GetTopLevel(mainWindow);
            if (topLevel == null)
            {
                return;
            }
            var storageProvider = topLevel.StorageProvider;

            // 异步的保存文件。
            var resultFile = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
            {
                AllowMultiple = false,
                Title = "选择文件夹",
            });

            // 这是有可能为空的。
            if (resultFile == null || resultFile.Count == 0) return;

            Stopwatch sw = new Stopwatch();
            sw.Start();

            CurrentFilePath = resultFile[0].Path.LocalPath;

            //var property = await DataManager.ReadDataProperty(@"D:\Projects\Daq\DataAnalysis\bin\Debug\net8.0\data\20241121-142741.dxd");
            var data = await DataManager.ReadMetaData(CurrentFilePath);
            CurrentMetaData = data;
            sw.Stop();
            Debug.WriteLine($"打开文件耗时{sw.ElapsedMilliseconds}ms");
            //热力图
            if(data == null || data.Channels == null)
            {
                return;
            }
            var ch = data.Channels[0];
            if (ch.Properties == null)
            {
                return;
            }
            var count = ch.Properties.Count;
            
            List<WeightedPoint> points = [];
            for(int i = 0; i < count; i++)
            {
                for(int j = 0; j < 10; j++)
                {
                    if(i * 10 + j >= count)
                    {
                        break;
                    }
                    var property = ch.Properties[i * 10 + j];
                    points.Add(new WeightedPoint(j, i, property.TriggerCount));
                }
            }

            //趋势图
            //TrendSeries[0].Values = data.Channels[0].Properties.Select(t => (double)t.Average).ToList();
            TrendData.StartTime = data.StartTime;
            TrendData.Values = null;
            WeakReferenceMessenger.Default.Send(new TrendDataUpdatedMessage { });
        }

        public async Task ReadMetaData(string? fileName)
        {
            if (fileName == null) return;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            var data = await DataManager.ReadMetaData(Path.Combine(DataRootDirectory, fileName));
            sw.Stop();
            Debug.WriteLine($"打开文件耗时{sw.ElapsedMilliseconds}ms");
            CurrentMetaData = data;
            if(data == null)
            {
                return;
            }
            TrendData.Values = null;
            TrendData.TriggerCounts = null;
            ChannelNames = null;
            SelectedChannelName = null;
            TrendData.StartTime = data.StartTime;
            if(data.Channels == null || data.Channels.Count == 0)
            {
                return;
            }
            
            ChannelNames = data.Channels.Select(t => (string)t.Info.Name).ToList();
            SelectedChannelName = ChannelNames.FirstOrDefault();
            WeakReferenceMessenger.Default.Send(new TrendDataUpdatedMessage { });
        }

        partial void OnSelectedChannelNameChanged(string? value)
        {
            GetChannelData(value);
        }

        private void GetChannelData(string? channelName)
        {
            TrendData.Values = null;
            TrendData.TriggerCounts = null;
            if (CurrentMetaData == null || CurrentMetaData.Channels == null)
            {
                WeakReferenceMessenger.Default.Send(new TrendDataUpdatedMessage { });
                return;
            };
           
            var ch = CurrentMetaData.Channels.FirstOrDefault(c => c.Info.Name == channelName);
            if(ch == null || ch.Properties == null)
            {
                WeakReferenceMessenger.Default.Send(new TrendDataUpdatedMessage { });
                return;
            }

            int digits = ch.Info.Digits;
            double divider = Math.Pow(10, digits);
            TrendData.Values = ch.Properties.Select(t => (double)t.Average / divider).ToArray();
            TrendData.TriggerCounts = ch.Properties.Select(t => (double)t.TriggerCount).ToArray();
            WeakReferenceMessenger.Default.Send(new TrendDataUpdatedMessage { });
            return;
        }



        [ObservableProperty]
        private double exportProgress;
        [RelayCommand]
        private async Task ExportData()
        {
            if (string.IsNullOrWhiteSpace(SelectedDataFileName))
            {
                return;
            }
            var mainWindow = App.Current.Services.GetService<MainWindow>();
            var topLevel = TopLevel.GetTopLevel(mainWindow);
            if (topLevel == null)
            {
                return;
            }
            var storageProvider = topLevel.StorageProvider;

            // 异步的保存文件。
            var resultFile = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
            {
                Title = "选择文件夹",
                AllowMultiple = false
            });

            // 这是有可能为空的。
            if (resultFile == null ) return;
            Progress<double> progress = new(ReportProgress);
            try
            {
                await DataManager.CopyFileAsyncWithProgress(Path.Combine(DataRootDirectory, SelectedDataFileName), resultFile[0].Path.LocalPath, progress);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("文件到处失败：" + ex.Message);
                ReportProgress(0);
            }
        }

        private void ReportProgress(double val)
        {
            ExportProgress = val;
        }

        [RelayCommand]
        private async Task OpenTriggerSettingsDialog()
        {
            var vm = App.Current.Services.GetService<TriggerSettingsViewModel>();
            if (vm == null) return;
            var Result = await Dialog.ShowCustomModal<TriggerSettingsView, TriggerSettingsViewModel, bool>(
                    vm, options: new DialogOptions()
                    {
                        ShowInTaskBar = false
                    });
        }
    }

    public class TrendData
    {
        public DateTime StartTime { get; set; }
        private object valueLockObj = new();
        private double[]? values;
        public double[]? Values 
        {
            get
            {
                lock(valueLockObj)
                {
                    return values;
                }
            }
            set
            {
                lock(valueLockObj)
                {
                    values = value;
                }
            }
        }

        private object triggerCountsLockObj = new();
        private double[]? triggerCounts;
        public double[]? TriggerCounts
        {
            get
            {
                lock(triggerCountsLockObj)
                {
                    return triggerCounts;
                }
            }
            set
            {
                lock(triggerCountsLockObj)
                {
                    triggerCounts = value;
                }
            }
        }
    }

    public class TrendDataUpdatedMessage
    {

    }

    public partial class TrendDataPlotSettings : ObservableObject
    {
        [ObservableProperty]
        private bool useAbsolutelyTime;
    }
}
